package com.tianji.promotion.service.impl;

import cn.hutool.core.util.ObjectUtil;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.tianji.common.autoconfigure.redisson.annotations.Lock;
import com.tianji.common.domain.dto.PageDTO;
import com.tianji.common.exceptions.BadRequestException;
import com.tianji.common.exceptions.BizIllegalException;
import com.tianji.common.utils.BeanUtils;
import com.tianji.common.utils.CollUtils;
import com.tianji.common.utils.ObjectUtils;
import com.tianji.common.utils.UserContext;
import com.tianji.promotion.domain.dto.CouponDiscountDTO;
import com.tianji.promotion.domain.dto.OrderCourseDTO;
import com.tianji.promotion.domain.po.Coupon;
import com.tianji.promotion.domain.po.CouponScope;
import com.tianji.promotion.domain.po.ExchangeCode;
import com.tianji.promotion.domain.po.UserCoupon;
import com.tianji.promotion.domain.vo.CouponVO;
import com.tianji.promotion.enums.CouponStatus;
import com.tianji.promotion.enums.ExchangeCodeStatus;
import com.tianji.promotion.enums.UserCouponStatus;
import com.tianji.promotion.mapper.CouponMapper;
import com.tianji.promotion.mapper.UserCouponMapper;
import com.tianji.promotion.query.UserCouponQuery;
import com.tianji.promotion.service.IExchangeCodeService;
import com.tianji.promotion.service.IUserCouponService;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.tianji.promotion.strategy.discount.Discount;
import com.tianji.promotion.strategy.discount.DiscountStrategy;
import com.tianji.promotion.utils.CodeUtil;
import com.tianji.promotion.utils.PermuteUtil;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.aop.framework.AopContext;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.time.LocalDateTime;
import java.util.*;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.Executor;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

/**
 * <p>
 * 用户领取优惠券的记录，是真正使用的优惠券信息 服务实现类
 * </p>
 *
 * @author lcm
 * @since 2023-08-03
 */
@Service
@RequiredArgsConstructor
@Slf4j
public class UserCouponServiceImpl extends ServiceImpl<UserCouponMapper, UserCoupon> implements IUserCouponService {

    private final CouponMapper couponMapper;
    private final IExchangeCodeService exchangeCodeService;
    private final UserCouponMapper userCouponMapper;
    private final CouponScopeServiceImpl couponScopeService;
    private final Executor discountSolutionExecutor;

    /**
     * 领取优惠券
     *
     * @param id
     */
    @Override
    @Lock(name = "lock:coupon:#{id}")
    @Transactional
    public void receiveCoupon(Long id) {
        //1.查询优惠券
        Coupon coupon = couponMapper.selectById(id);
        if(ObjectUtils.isNull(coupon)){
            throw new BadRequestException("优惠券不存在！");
        }
        //2.校验是否是正在发放状态
        LocalDateTime now = LocalDateTime.now();
        if(now.isBefore(coupon.getIssueBeginTime()) || now.isAfter(coupon.getIssueEndTime())){
            throw new BadRequestException("优惠券发放已结束或未开始！");
        }
        //3.是否库存充足
        if(coupon.getIssueNum().equals(coupon.getTotalNum())){
            throw new BadRequestException("优惠券库存不足！");
        }
        //4.校验并且生成用户券
        Long userId = UserContext.getUser();
        //4.1通过AopContext拿到代理对象
//        UserCouponServiceImpl userCouponService = (UserCouponServiceImpl) AopContext.currentProxy();
//        //4.2加悲观锁
//        userCouponService.checkAndCreateUserCoupon(coupon,userId);
        checkAndCreateUserCoupon(coupon,userId);
    }
    /**
     * 校验并且生成用户券
     */
    @Transactional
    public void checkAndCreateUserCoupon(Coupon coupon,Long userId) {
        //1.是否超出限领数量
        //1.1 先统计当前用户领取的此优惠券数量
        Integer count = lambdaQuery()
                .eq(UserCoupon::getUserId, userId)
                .eq(UserCoupon::getCouponId, coupon.getId())
                .count();
        if(count !=null && coupon.getUserLimit() <= count){
            throw new BadRequestException("超出领取数量！");
        }
        //2.发放数量+1
        int result=couponMapper.icsIssueNum(coupon.getId());
        if(result == 0){
            throw new BadRequestException("优惠券库存不足！");
        }
        //3.生成用户券
        saveUserCoupon(coupon,userId);
    }

    /**
     * 生成用户券
     */
    private void saveUserCoupon(Coupon coupon, Long userId) {
        // 1.基本信息
        UserCoupon uc = new UserCoupon();
        uc.setUserId(userId);
        uc.setCouponId(coupon.getId());
        // 2.有效期信息
        LocalDateTime termBeginTime = coupon.getTermBeginTime();
        LocalDateTime termEndTime = coupon.getTermEndTime();
        if (termBeginTime == null) {
            termBeginTime = LocalDateTime.now();
            termEndTime = termBeginTime.plusDays(coupon.getTermDays());
        }
        uc.setTermBeginTime(termBeginTime);
        uc.setTermEndTime(termEndTime);
        // 3.保存
        save(uc);
    }

    /**
     * 兑换兑换码
     *
     * @param code
     */
    @Override
    @Transactional
    public void exchangeCoupon(String code) {
        //1.校验并解析兑换码
        long serialNum = CodeUtil.parseCode(code);
        boolean isExchange = exchangeCodeService.updateExchangeMark(serialNum,true);
        if(isExchange){
            throw new BizIllegalException("兑换码已经被兑换过了");
        }
        try {
            //2.查询兑换码是否存在
            ExchangeCode exchangeCode = exchangeCodeService.getById(serialNum);
            if (ObjectUtils.isNull(exchangeCode)) {
                throw new BizIllegalException("兑换码不存在！");
            }
            //3.查询兑换码是否过期
            if (LocalDateTime.now().isAfter(exchangeCode.getExpiredTime())) {
                throw new BizIllegalException("兑换码已过期了");
            }
            //4.查询兑换码是否超出限领数量，校验并且生成用户券
            Coupon coupon = couponMapper.selectById(exchangeCode.getExchangeTargetId());
            if (ObjectUtil.isNull(coupon)) {
                throw new BizIllegalException("兑换码兑换的优惠券不存在！");
            }
            Long userId = UserContext.getUser();
            checkAndCreateUserCoupon(coupon, userId);
            //5.更新兑换码状态
            exchangeCodeService.lambdaUpdate()
                    .set(ExchangeCode::getStatus, ExchangeCodeStatus.USED)
                    .set(ExchangeCode::getUserId, UserContext.getUser())
                    .eq(ExchangeCode::getId, serialNum)
                    .update();

        }catch (Exception e){
            exchangeCodeService.updateExchangeMark(serialNum,false);
            throw e;
        }
    }

    /**
     * 分页查询我的优惠券
     *
     * @param query
     * @return
     */
    @Override
    public PageDTO<CouponVO> pageCoupon(UserCouponQuery query) {
        //1.优惠券状态
        UserCouponStatus status = query.getStatus();
        Long userId = UserContext.getUser();
        //2.查询
        Page<UserCoupon> page = lambdaQuery()
                .eq(UserCoupon::getStatus, status)
                .eq(UserCoupon::getUserId, userId)
                .page(query.toMpPageDefaultSortByCreateTimeDesc());
        List<UserCoupon> records = page.getRecords();
        //2.1健壮性处理
        if(CollUtils.isEmpty(records)){
            return PageDTO.empty(page);
        }
        //3.封装VO
        List<Long> couponIds = records.stream().map(UserCoupon::getCouponId).collect(Collectors.toList());
        if(CollUtils.isEmpty(couponIds)){
            throw new BadRequestException("优惠券信息错误！");
        }
        List<Coupon> coupons = couponMapper.selectBatchIds(couponIds);
        if(CollUtils.isEmpty(coupons)){
            throw new BadRequestException("优惠券错误！");
        }
        List<CouponVO> vos = coupons.stream().map(coupon -> BeanUtils.copyBean(coupon, CouponVO.class)).collect(Collectors.toList());
        //4.返回PageDTO
        return PageDTO.of(page,vos);
    }

    /**
     * 查询我的优惠券可用方案
     *
     * @param oList
     * @return
     */
    @Override
    public List<CouponDiscountDTO> findSolution(List<OrderCourseDTO> oList) {
        //1.查询所有用户券
        Long userId = UserContext.getUser();
        List<Coupon> coupons= userCouponMapper.queryMyCoupons(userId);
        //1.1健壮性判断
        if(CollUtils.isEmpty(coupons)){
            throw new BizIllegalException("用户不存在优惠券！");
        }
        //2.初筛
        //2.1 订单总价
        int total = oList.stream().mapToInt(OrderCourseDTO::getPrice).sum();
        //2.2 筛选优惠券
        List<Coupon> couponList = coupons.stream()
                .filter(c -> DiscountStrategy.getDiscount(c.getDiscountType()).canUse(total, c))
                .collect(Collectors.toList());
        if(CollUtils.isEmpty(couponList)){
            return CollUtils.emptyList();
        }
        // 3.排列组合出所有方案
        // 3.1.细筛（找出每一个优惠券的可用的课程，判断课程总价是否达到优惠券的使用需求）
        Map<Coupon, List<OrderCourseDTO>> map=findAvailableCoupon(coupons,oList);
        if(CollUtils.isEmpty(map)){
            return CollUtils.emptyList();
        }
        // 3.2.排列组合
        couponList = new ArrayList<>(map.keySet());
        List<List<Coupon>> solutions = PermuteUtil.permute(couponList);
        // 3.3.添加单券的方案
        for (Coupon c : couponList) {
            solutions.add(List.of(c));
        }
        // 4.计算方案的优惠明细
        List<CouponDiscountDTO> list = Collections.synchronizedList(new ArrayList<>(solutions.size()));
        // 4.1.定义闭锁
        CountDownLatch latch = new CountDownLatch(solutions.size());
        for (List<Coupon> solution : solutions) {
            // 4.2.异步计算
            CompletableFuture
                    .supplyAsync(
                            () -> calculateSolutionDiscount(map, oList, solution),
                            discountSolutionExecutor
                    ).thenAccept(dto -> {
                // 4.3.提交任务结果
                list.add(dto);
                latch.countDown();
            });
        }
        // 4.4.等待运算结束
        try {
            latch.await(1, TimeUnit.SECONDS);
        } catch (InterruptedException e) {
            log.error("优惠方案计算被中断，{}", e.getMessage());
        }


        // 5.筛选最优解
        return findBestSolution(list);
    }

    /**
     * 可用优惠券
     * @param coupons
     * @param oList
     * @return
     */
    private Map<Coupon, List<OrderCourseDTO>> findAvailableCoupon(List<Coupon> coupons, List<OrderCourseDTO> oList) {
        //1.
        Map<Coupon, List<OrderCourseDTO>> map = new HashMap<>(coupons.size());
        for (Coupon coupon : coupons) {
            //1.1判断是否存在限定范围
            List<OrderCourseDTO> availableCourses = oList;
            if (coupon.getSpecific()) {
                List<CouponScope> list = couponScopeService.lambdaQuery()
                        .eq(CouponScope::getId, coupon.getId())
                        .list();
                //获取业务id集合
                Set<Long> scopes = list.stream().map(CouponScope::getBizId).collect(Collectors.toSet());

                availableCourses =oList.stream()
                        .filter(c -> scopes.contains(c.getCateId()))
                        .collect(Collectors.toList());
            }
            if(CollUtils.isEmpty(availableCourses)){
                continue;
            }
            //2. 计算课程总价
            int totalAmount = availableCourses.stream().mapToInt(OrderCourseDTO::getPrice).sum();

            Discount discount = DiscountStrategy.getDiscount(coupon.getDiscountType());
            //3.判断是否可用
            if(discount.canUse(totalAmount,coupon)){
                map.put(coupon,availableCourses);
            }

        }

        return map;
    }

    /**
     * 计算优惠折扣
     * @param map
     * @param oList
     * @param solution
     */
    private CouponDiscountDTO calculateSolutionDiscount(Map<Coupon, List<OrderCourseDTO>> map,
                                                        List<OrderCourseDTO> oList, List<Coupon> solution) {
        // 1.初始化DTO
        CouponDiscountDTO dto = new CouponDiscountDTO();
        // 2.初始化折扣明细的映射
        Map<Long, Integer> detailMap = oList.stream().collect(Collectors.toMap(OrderCourseDTO::getId, oc -> 0));
        // 3.计算折扣
        for (Coupon coupon : solution) {
            // 3.1.获取优惠券限定范围对应的课程
            List<OrderCourseDTO> availableCourses = map.get(coupon);
            // 3.2.计算课程总价(课程原价 - 折扣明细)
            int totalAmount = availableCourses.stream()
                    .mapToInt(oc -> oc.getPrice() - detailMap.get(oc.getId())).sum();
            // 3.3.判断是否可用
            Discount discount = DiscountStrategy.getDiscount(coupon.getDiscountType());
            if (!discount.canUse(totalAmount, coupon)) {
                // 券不可用，跳过
                continue;
            }
            // 3.4.计算优惠金额
            int discountAmount = discount.calculateDiscount(totalAmount, coupon);
            // 3.5.计算优惠明细
            calculateDiscountDetails(detailMap, availableCourses, totalAmount, discountAmount);
            // 3.6.更新DTO数据
            dto.getIds().add(coupon.getCreater());
            dto.getRules().add(discount.getRule(coupon));
            dto.setDiscountAmount(discountAmount + dto.getDiscountAmount());
        }
        return dto;
    }

    private void calculateDiscountDetails(Map<Long, Integer> detailMap, List<OrderCourseDTO> courses,
                                          int totalAmount, int discountAmount) {
        int times = 0;
        int remainDiscount = discountAmount;
        for (OrderCourseDTO course : courses) {
            // 更新课程已计算数量
            times++;
            int discount = 0;
            // 判断是否是最后一个课程
            if (times == courses.size()) {
                // 是最后一个课程，总折扣金额 - 之前所有商品的折扣金额之和
                discount = remainDiscount;
            } else {
                // 计算折扣明细（课程价格在总价中占的比例，乘以总的折扣）
                discount = discountAmount * course.getPrice() / totalAmount;
                remainDiscount -= discount;
            }
            // 更新折扣明细
            detailMap.put(course.getId(), discount + detailMap.get(course.getId()));
        }
    }

    private List<CouponDiscountDTO> findBestSolution(List<CouponDiscountDTO> list) {
        // 1.准备Map记录最优解
        Map<String, CouponDiscountDTO> moreDiscountMap = new HashMap<>();
        Map<Integer, CouponDiscountDTO> lessCouponMap = new HashMap<>();
        // 2.遍历，筛选最优解
        for (CouponDiscountDTO solution : list) {
            // 2.1.计算当前方案的id组合
            String ids = solution.getIds().stream()
                    .sorted(Long::compare).map(String::valueOf).collect(Collectors.joining(","));
            // 2.2.比较用券相同时，优惠金额是否最大
            CouponDiscountDTO best = moreDiscountMap.get(ids);
            if (best != null && best.getDiscountAmount() >= solution.getDiscountAmount()) {
                // 当前方案优惠金额少，跳过
                continue;
            }
            // 2.3.比较金额相同时，用券数量是否最少
            best = lessCouponMap.get(solution.getDiscountAmount());
            int size = solution.getIds().size();
            if (size > 1 && best != null && best.getIds().size() <= size) {
                // 当前方案用券更多，放弃
                continue;
            }
            // 2.4.更新最优解
            moreDiscountMap.put(ids, solution);
            lessCouponMap.put(solution.getDiscountAmount(), solution);
        }
        // 3.求交集
        Collection<CouponDiscountDTO> bestSolutions = CollUtils
                .intersection(moreDiscountMap.values(), lessCouponMap.values());
        // 4.排序，按优惠金额降序
        return bestSolutions.stream()
                .sorted(Comparator.comparingInt(CouponDiscountDTO::getDiscountAmount).reversed())
                .collect(Collectors.toList());
    }
}
